home *** CD-ROM | disk | FTP | other *** search
- /* net/rom level 4 (transport) protocol implementation
- * Copyright 1989 by Daniel M. Frank, W9NK. Permission granted for
- * non-commercial distribution only.
- */
-
- #include <stdio.h>
- #include "global.h"
- #include "mbuf.h"
- #include "timer.h"
- #include "ax25.h"
- #include "lapb.h"
- #include "netrom.h"
- #include "nr4.h"
- #include <ctype.h>
-
- #undef NR4DEBUG
-
- /* Globals: */
-
- /* The circuit table */
-
- struct nr4circp Nr4circuits[NR4MAXCIRC] ;
-
- /* Various limits */
-
- unsigned Nr4window = 4 ; /* Max window to negotiate */
- unsigned Nr4retries = 10 ; /* Max retries */
- unsigned Nr4qlimit = 2048 ; /* Max bytes on receive queue */
-
- /* Timers */
-
- long Nr4irtt = 15000 ; /* Initial round trip time */
- long Nr4acktime = 3000 ; /* ACK delay timer */
- long Nr4choketime = 180000 ; /* CHOKEd state timeout */
-
-
- /* This function is called when a net/rom layer four frame */
- /* is discovered inside a datagram addressed to us */
-
- void
- nr4input(hdr,bp)
- struct nr4hdr *hdr ;
- struct mbuf *bp ;
- {
- struct nr4hdr rhdr ;
- struct nr4cb *cb ;
- struct ax25_addr dest ;
- int op ;
- unsigned window ;
- int acceptc ; /* indicates that connection should be accepted */
- int newconn ; /* indicates that this is a new incoming */
- /* connection. You'll see. */
- int gotchoke ; /* The choke flag was set in this packet */
- int i ;
-
- op = hdr->opcode & NR4OPCODE ; /* Mask off flags */
-
- if (op == NR4OPCONRQ) { /* process connect request first */
- acceptc = 1 ;
- newconn = 0 ;
-
- /* These fields are sent regardless of success */
-
- rhdr.yourindex = hdr->u.conreq.myindex ;
- rhdr.yourid = hdr->u.conreq.myid ;
- dest = hdr->u.conreq.node ;
-
- /* Check to see if we have already received a connect */
- /* request for this circuit. */
-
- if ((cb = match_n4circ(hdr->u.conreq.myindex, hdr->u.conreq.myid,
- &hdr->u.conreq.user, &hdr->u.conreq.node))
- == NULLNR4CB) { /* No existing circuit if NULL */
-
- /* Try to get a new circuit */
-
- if ((cb = new_n4circ()) == NULLNR4CB) {
- acceptc = 0 ;
- } else {
-
- /* Window is set to min of the offered and local windows */
-
- window = hdr->u.conreq.window > Nr4window ?
- Nr4window : hdr->u.conreq.window ;
-
- if (init_nr4window(cb, window) == -1) {
- free_n4circ(cb) ;
- acceptc = 0 ;
- } else {
- /* Set up control block */
- cb->yournum = hdr->u.conreq.myindex ;
- cb->yourid = hdr->u.conreq.myid ;
- cb->user = hdr->u.conreq.user ;
- cb->node = hdr->u.conreq.node ;
- cb->luser = mycall ;/* we are local user on incomings */
- cb->srtt = Nr4irtt ;/* Default round trip time */
- nr4defaults(cb) ; /* set up timers, window pointers */
- cb->s_upcall = nr4_incom ;
- cb->state = NR4STDISC ;
- newconn = 1 ;
- } /* End if window successfully allocated */
-
- } /* End if new circuit available */
-
- } /* End if no existing circuit matching parameters */
-
- /* Now set up response */
-
- if (!acceptc) {
- rhdr.opcode = NR4OPCONAK | NR4CHOKE ;/* choke means reject */
- rhdr.u.conack.myindex = 0 ;
- rhdr.u.conack.myid = 0 ;
- rhdr.u.conack.window = 0 ;
- } else {
- rhdr.opcode = NR4OPCONAK ;
- rhdr.u.conack.myindex = cb->mynum ;
- rhdr.u.conack.myid = cb->myid ;
- rhdr.u.conack.window = cb->window ;
- }
- nr4sframe(&dest, &rhdr, NULLBUF) ;
-
- /* Why, you ask, do we wait until now for the state change */
- /* upcall? Well, it's like this: if the state change triggers */
- /* something like the mailbox to send its banner, the banner */
- /* would have gone out *before* the conn ack if we'd done this */
- /* in the code above. This is what happens when you don't plan */
- /* too well. Learn from my mistakes :-) */
-
- if (newconn)
- nr4state(cb, NR4STCON) ;/* connected (no 3-way handshake) */
-
- free_p(bp) ;
- return ;
- } /* end connect request code */
-
- /* validate circuit number */
-
- if ((cb = get_n4circ(hdr->yourindex, hdr->yourid)) == NULLNR4CB) {
- free_p(bp) ;
- return ;
- }
-
- /* Check for choke flag */
-
- if (hdr->opcode & NR4CHOKE)
- gotchoke = 1 ;
- else
- gotchoke = 0 ;
-
- /* Here's where the interesting stuff gets done */
-
- switch (cb->state) {
- case NR4STCPEND:
- switch (op) {
- case NR4OPCONAK:
- stop_timer(&cb->tcd) ;
- if (gotchoke) { /* connect rejected */
- cb->dreason = NR4RREFUSED ;
- nr4state(cb, NR4STDISC) ;
- break ;
- }
- cb->yournum = hdr->u.conack.myindex ;
- cb->yourid = hdr->u.conack.myid ;
- window = hdr->u.conack.window > Nr4window ?
- Nr4window : hdr->u.conack.window ;
-
- if (init_nr4window(cb, window) == -1) {
- cb->dreason = NR4RRESET ;
- nr4state(cb, NR4STDISC) ;
- } else {
- nr4defaults(cb) ; /* set up timers, window pointers */
-
- if (cb->cdtries == 1) /* No retries */
- cb->srtt = cb->tcd.count * MSPTICK ;/* Use measured rtt */
- else
- cb->srtt = Nr4irtt ; /* else use default */
-
- nr4state(cb, NR4STCON) ;
- nr4output(cb) ; /* start sending anything on the txq */
- }
- break ;
-
- default: /* We can't respond to anything else without */
- /* Their ID and index */
- free_p(bp) ;
- return ;
- }
- break ;
-
- case NR4STCON:
- switch (op) {
- case NR4OPDISRQ:
- /* format reply packet */
- rhdr.opcode = NR4OPDISAK ;
- rhdr.yourindex = cb->yournum ;
- rhdr.yourid = cb->yourid ;
-
- nr4sframe(&cb->node, &rhdr, NULLBUF) ;
-
- cb->dreason = NR4RREMOTE ;
- nr4state(cb, NR4STDISC) ;
-
- break ;
-
- case NR4OPINFO:
- /* Do receive frame processing */
- nr4rframe(cb, hdr->u.info.txseq, bp) ;
-
- /* Reset the choke flag if no longer choked. Processing */
- /* the ACK will kick things off again. */
-
- if (cb->choked && !gotchoke) {
- stop_timer(&cb->tchoke) ;
- cb->choked = 0 ;
- }
-
- /* We delay processing the receive sequence number until */
- /* now, because the ACK might pull more off the txq and send */
- /* it, and we want the implied ACK in those frames to be right */
-
- /* Only process NAKs if the choke flag is off. It appears */
- /* that NAKs should never be sent with choke on, by the way, */
- /* but you never know, considering that there is no official */
- /* standard for this protocol */
-
- if (hdr->opcode & NR4NAK && !gotchoke)
- nr4gotnak(cb, hdr->u.info.rxseq) ;
-
- /* We always do ACK processing, too, since the NAK of one */
- /* packet may be the implied ACK of another. The gotchoke */
- /* flag is used to prevent sending any new frames, since */
- /* we are just going to purge them next anyway if this is */
- /* the first time we've seen the choke flag. If we are */
- /* already choked, this call will return immediately. */
-
- nr4ackours(cb, hdr->u.info.rxseq, gotchoke) ;
-
- /* If we haven't seen the choke flag before, purge the */
- /* send window and set the timer and the flag. */
-
- if (!cb->choked && gotchoke)
- nr4choke(cb) ;
-
- break ;
-
- case NR4OPACK:
- if (cb->choked && !gotchoke) { /* clear choke if appropriate */
- stop_timer(&cb->tchoke) ;
- cb->choked = 0 ;
- }
-
- if (hdr->opcode & NR4NAK && !gotchoke)
- nr4gotnak(cb, hdr->u.ack.rxseq) ; /* process NAKs */
-
- nr4ackours(cb, hdr->u.ack.rxseq, gotchoke) ; /* and ACKs */
-
- if (!cb->choked && gotchoke) /* First choke seen */
- nr4choke(cb) ; /* Set choke status */
-
- break ;
- }
- break ;
-
- case NR4STDPEND:
- switch (op) {
- case NR4OPDISAK:
- cb->dreason = NR4RNORMAL ;
- nr4state(cb, NR4STDISC) ;
- break ;
-
- case NR4OPINFO:
- /* We can still do receive frame processing until */
- /* the disconnect acknowledge arrives, but we won't */
- /* bother to process ACKs, since we've flushed our */
- /* transmit buffers and queue already. */
-
- nr4rframe(cb, hdr->u.info.txseq, bp) ;
-
- break ;
- }
-
- } /* End switch(state) */
-
- }
-
-
- /* Send a net/rom layer 4 frame. bp should be NULLBUF unless the frame
- * type is info.
- */
-
- void
- nr4sframe(dest, hdr, bp)
- struct ax25_addr *dest ;
- struct nr4hdr *hdr ;
- struct mbuf *bp ;
- {
- struct mbuf *n4b ;
-
- if ((n4b = htonnr4(hdr)) == NULLBUF) {
- free_p(bp) ;
- return ;
- } else {
- append(&n4b, bp) ;
- nr3output(dest, n4b) ;
- }
- }
-
- /* Receive frame processing */
-
- void
- nr4rframe(cb, rxseq, bp)
- struct nr4cb *cb ;
- unsigned rxseq ;
- struct mbuf *bp ;
- {
- struct nr4hdr rhdr ;
- unsigned window = cb->window ;
- unsigned rxbuf = rxseq % window ;
- unsigned newdata = 0 ; /* whether to upcall */
-
- #ifdef NR4DEBUG
- printf("Processing received info\n") ;
- #endif
-
- /* If we're choked, just reset the ACK timer to blast out */
- /* another CHOKE indication after the ackdelay */
-
- if (cb->qfull) {
- start_timer(&cb->tack) ;
- return ;
- }
-
- /* If the frame is out of sequence, NAK it if we haven't */
- /* already done so */
-
- if (rxseq != cb->rxpected && !cb->naksent) {
- #ifdef NR4DEBUG
- printf("Frame out of sequence -- expected %u, got %u.\n",
- cb->rxpected, rxseq) ;
- #endif
- rhdr.opcode = NR4OPACK | NR4NAK ;
- rhdr.yourindex = cb->yournum ;
- rhdr.yourid = cb->yourid ;
- rhdr.u.ack.rxseq = cb->rxpected ;
- nr4sframe(&cb->node, &rhdr, NULLBUF) ;
-
- /* Now make sure we don't send any more of these until */
- /* we see some good data. Otherwise full window retransmissions */
- /* would result in a flurry of NAKs */
-
- cb->naksent = 1 ;
- }
-
- /* If this is a new frame, within the window, buffer it, */
- /* then see what we can deliver */
-
- if (nr4between(cb->rxpected,rxseq,cb->rxpastwin)
- && !cb->rxbufs[rxbuf].occupied) {
- #ifdef NR4DEBUG
- printf("Frame within window\n") ;
- #endif
- cb->rxbufs[rxbuf].occupied = 1 ;
- cb->rxbufs[rxbuf].data = bp ;
-
- for (rxbuf = cb->rxpected % window ; cb->rxbufs[rxbuf].occupied ;
- rxbuf = cb->rxpected % window) {
- #ifdef NR4DEBUG
- printf("Removing frame from buffer %d\n", rxbuf) ;
- #endif
- newdata = 1 ;
- cb->rxbufs[rxbuf].occupied = 0 ;
- append(&cb->rxq,cb->rxbufs[rxbuf].data) ;
- cb->rxbufs[rxbuf].data = NULLBUF ;
- cb->rxpected = (cb->rxpected + 1) & NR4SEQMASK ;
- cb->rxpastwin = (cb->rxpastwin + 1) & NR4SEQMASK ;
- }
- if (newdata) {
- cb->naksent = 0 ; /* OK to send NAKs again */
- if (cb->r_upcall != NULLVFP)
- (*cb->r_upcall)(cb,len_mbuf(cb->rxq)) ;
-
- /* Now that our upcall has had a shot at the queue, */
- /* see if it's past the queue length limit. If so, */
- /* go into choked mode (i.e. flow controlled). */
-
- if (len_mbuf(cb->rxq) > Nr4qlimit) {
- cb->qfull = 1 ;
- nr4ackit((char *)cb) ; /* Tell `em right away */
- } else
- start_timer(&cb->tack) ;
- }
- } else /* It's out of the window or we've seen it already */
- free_p(bp) ;
- }
-
-
- /* Send the transmit buffer whose sequence number is seq */
-
- void
- nr4sbuf(cb, seq)
- struct nr4cb *cb ;
- unsigned seq ;
- {
- struct nr4hdr hdr ;
- struct mbuf *bufbp, *bp ;
- unsigned bufnum = seq % cb->window ;
- struct timer *t ;
-
- /* sanity check */
-
- if (bufnum >= cb->window) {
- #ifdef NRDEBUG
- printf("sbuf: buffer number %u beyond window\n",bufnum) ;
- #endif
- return ;
- }
-
- /* Stop the ACK timer, since our sending of the frame is */
- /* an implied ACK. */
-
- stop_timer(&cb->tack) ;
-
- /* Duplicate the mbuf, since we have to keep it around */
- /* until it is acknowledged */
-
- bufbp = cb->txbufs[bufnum].data ;
-
- /* Notice that we use copy_p instead of dup_p. This is because */
- /* a frame can still be sitting on the AX.25 send queue when it */
- /* get acknowledged, and we don't want to deallocate its data */
- /* before it gets sent! */
-
- if ((bp = copy_p(bufbp, len_mbuf(bufbp))) == NULLBUF) {
- free_mbuf(bp) ;
- return ;
- }
-
- /* Prepare the header */
- if (cb->qfull) /* are we choked? */
- hdr.opcode = NR4OPINFO | NR4CHOKE ;
- else
- hdr.opcode = NR4OPINFO ;
- hdr.yourindex = cb->yournum ;
- hdr.yourid = cb->yourid ;
- hdr.u.info.txseq = (unsigned char)(seq & NR4SEQMASK) ;
- hdr.u.info.rxseq = cb->rxpected ;
-
- /* Send the frame, then set and start the timer */
-
- nr4sframe(&cb->node, &hdr, bp) ;
-
- t = &cb->txbufs[bufnum].tretry ;
- t->start = (1 << cb->blevel) *
- (2 * cb->mdev + cb->srtt + MSPTICK) / MSPTICK ;
- start_timer(t) ;
-
- return ;
- }
-
- /* Check to see if any of our frames have been ACKed */
-
- void
- nr4ackours(cb, seq, gotchoke)
- struct nr4cb *cb ;
- unsigned seq ;
- int gotchoke ; /* The choke flag is set in the received frame */
- {
- unsigned txbuf ;
- struct timer *t ;
-
- /* If we are choked, there is nothing in the send window */
- /* by definition, so we can just return. */
-
- if (cb->choked)
- return ;
-
- /* Adjust seq to point to the frame being ACK'd, not the one */
- /* beyond it, which is how it arrives. */
-
- seq = (seq - 1) & NR4SEQMASK ;
-
- /* Free up all the ack'd frames, and adjust the round trip */
- /* timing stuff */
-
- while (nr4between(cb->ackxpected, seq, cb->nextosend)) {
- #ifdef NR4DEBUG
- printf("Sequence # %u acknowledged\n", seq) ;
- #endif
- cb->nbuffered-- ;
- txbuf = cb->ackxpected % cb->window ;
- stop_timer(&cb->txbufs[txbuf].tretry) ;
- free_mbuf(cb->txbufs[txbuf].data) ;
- cb->txbufs[txbuf].data = NULLBUF ;
- cb->ackxpected = (cb->ackxpected + 1) & NR4SEQMASK ;
-
- /* Round trip time estimation, cribbed from TCP */
-
- if (cb->txbufs[txbuf].retries == 0) { /* We only sent this one once */
- int32 rtt ;
- int32 abserr ;
-
- t = &cb->txbufs[txbuf].tretry ;
- rtt = (t->start - t->count) * MSPTICK ; /* get our rtt in msec */
- abserr = (rtt > cb->srtt) ? rtt - cb->srtt : cb->srtt - rtt ;
- cb->srtt = (cb->srtt * 7 + rtt) >> 3 ;
- cb->mdev = (cb->mdev * 3 + abserr) >> 2 ;
-
- /* Reset the backoff level */
-
- cb->blevel = 0 ;
- }
- }
-
- /* Now we recalculate tmax, the maximum number of retries for */
- /* any frame in the window. tmax is used as a baseline to */
- /* determine when the window has reached a new high in retries. */
- /* We don't want to increment blevel for every frame that times */
- /* out, since that would lead to us backing off too fast when */
- /* all the frame timers expired at around the same time. */
-
- cb->txmax = 0 ;
-
- for (seq = cb->ackxpected ;
- nr4between(cb->ackxpected, seq, cb->nextosend) ;
- seq = (seq + 1) & NR4SEQMASK)
- if (cb->txbufs[seq % cb->window].retries > cb->txmax)
- cb->txmax = cb->txbufs[seq % cb->window].retries ;
-
- /* This is kind of a hack. This function is called under */
- /* three different conditions: either we are choked, in */
- /* which case we return immediately, or we are not choked, */
- /* in which case we proceed normally to keep the send */
- /* window full, or we have seen the choke flag for the first */
- /* time. In the last case, gotchoke is true while cb->choked */
- /* is false. We want to process any acknowledgments of existing */
- /* frames in the send window before we purge it, while at the */
- /* same time we don't want to take anything else off the txq */
- /* or send it out. So, in the third case we listed, we return */
- /* now since we've processed the ACK. */
-
- if (gotchoke)
- return ;
-
- nr4output(cb) ; /* yank stuff off txq and send it */
-
- /* At this point, either the send window is full, or */
- /* nr4output() didn't find enough on the txq to fill it. */
- /* If the window is not full, then the txq must be empty, */
- /* and we'll make a tx upcall */
-
- if (cb->nbuffered < cb->window)
- if (cb->t_upcall != NULLVFP)
- (*cb->t_upcall)(cb, (cb->window - cb->nbuffered) * NR4MAXINFO) ;
-
- }
-
-
- /* If the send window is open and there are frames on the txq,
- * move as many as possible to the transmit buffers and send them.
- * Return the number of frames sent.
- */
-
- int
- nr4output(cb)
- struct nr4cb *cb ;
- {
- int numq, i ;
- struct mbuf *bp ;
- struct nr4txbuf *tp ;
-
- /* Are we in the proper state? */
-
- if (cb->state != NR4STCON || cb->choked)
- return 0 ; /* No sending if not connected */
- /* or if choked */
-
- /* See if the window is open */
-
- if (cb->nbuffered >= cb->window)
- return 0 ;
-
- numq = len_q(cb->txq) ;
-
- #ifdef NR4DEBUG
- printf("nr4output: %d packets on txq\n", numq) ;
- #endif
-
- for (i = 0 ; i < numq ; i++) {
-
- bp = dequeue(&cb->txq) ;
-
- #ifdef NR4DEBUG
- if (len_mbuf(bp) > NR4MAXINFO) { /* should be checked higher up */
- printf("Upper layers queued too big a buffer\n") ;
- continue ;
- }
- #endif
- /* Set up and send buffer */
-
- tp = &cb->txbufs[cb->nextosend % cb->window] ;
- tp->retries = 0 ;
- tp->data = bp ;
- nr4sbuf(cb, cb->nextosend) ;
-
- /* Update window and buffered count */
-
- cb->nextosend = (cb->nextosend + 1) & NR4SEQMASK ;
- if (++cb->nbuffered >= cb->window)
- break ;
- }
- return i ;
- }
-
- void
- nr4state(cb, newstate)
- struct nr4cb *cb ;
- int newstate ;
- {
- int i ;
- int oldstate = cb->state ;
-
- cb->state = newstate ;
-
- switch (cb->state) {
- case NR4STDPEND:
- stop_timer(&cb->tchoke);
-
- /* When we request a disconnect, we lose the contents of */
- /* our transmit queue and buffers, but we retain our ability */
- /* to receive any packets in transit until a disconnect */
- /* acknowledge arrives */
-
- free_q(&cb->txq) ;
-
- for (i = 0 ; i < cb->window ; i++) {
- free_mbuf(cb->txbufs[i].data) ;
- cb->txbufs[i].data = NULLBUF ;
- stop_timer(&cb->txbufs[i].tretry) ;
- }
-
- /* Tidy up stats: roll the top window pointer back */
- /* and reset nbuffered to reflect this. Not really */
- /* necessary, but leads to a bit more truth telling */
- /* in the status displays. */
-
- cb->nextosend = cb->ackxpected ;
- cb->nbuffered = 0 ;
-
- break ;
-
- case NR4STDISC:
- stop_timer(&cb->tchoke) ;
- stop_timer(&cb->tack) ;
- stop_timer(&cb->tcd) ;
-
- /* We don't clear the rxq, since the state change upcall */
- /* may pull something off of it at the last minute. */
-
- free_q(&cb->txq) ;
-
- /* The following loop will only be executed if the */
- /* window was set, since when the control block is */
- /* calloc'd the window field gets a 0 in it. This */
- /* protects us from dereferencing an unallocated */
- /* window buffer pointer */
-
- for (i = 0 ; i < cb->window ; i++) {
- free_mbuf(cb->rxbufs[i].data) ;
- cb->rxbufs[i].data = NULLBUF ;
- free_mbuf(cb->txbufs[i].data) ;
- cb->txbufs[i].data = NULLBUF ;
- stop_timer(&cb->txbufs[i].tretry) ;
- }
-
- break ;
- }
-
- if (oldstate != newstate && cb->s_upcall != NULLVFP)
- (*cb->s_upcall)(cb, oldstate, newstate) ;
-
- /* We take responsibility for deleting the circuit */
- /* descriptor. Don't do this anywhere else! */
-
- if (newstate == NR4STDISC)
- free_n4circ(cb) ;
-
- }
-
- /* Process NAKs. seq indicates the next frame expected by the
- * NAK'ing station.
- */
-
- void
- nr4gotnak(cb, seq)
- struct nr4cb *cb ;
- unsigned seq ;
- {
- if (nr4between(cb->ackxpected, seq, cb->nextosend))
- nr4sbuf(cb, seq) ;
- }
-
-
- /* This is called when we first get a CHOKE indication from the
- * remote. It purges the send window and sets the choke timer.
- */
-
- void
- nr4choke(cb)
- struct nr4cb *cb ;
- {
- int i ;
- unsigned seq ;
- struct mbuf *q, *bp ;
- struct nr4txbuf *t ;
-
- q = cb->txq ;
-
- /* We purge the send window, returning the buffers to the */
- /* txq in the proper order. */
-
- for (seq = (cb->nextosend - 1) & NR4SEQMASK ;
- nr4between(cb->ackxpected, seq, cb->nextosend) ;
- seq = (seq - 1) & NR4SEQMASK) {
-
- t = &cb->txbufs[seq % cb->window] ;
- stop_timer(&t->tretry) ;
- bp = t->data ;
- t->data = NULLBUF ;
- enqueue(&bp, q) ; /* prepend this packet to the queue */
- q = bp ;
- }
-
- cb->nextosend = cb->ackxpected ; /* close the window */
- cb->nbuffered = 0 ; /* nothing in the window */
- cb->txq = q ; /* Replace the txq with the one that has */
- /* the purged packets prepended */
- cb->choked = 1 ; /* Set the choked flag */
-
- start_timer(&cb->tchoke) ;
-
- }
-
-